/* Copyright (C) 2009  Versant Inc.   http://www.db4o.com */
package sharpen.core.framework;

import java.util.HashMap;
import java.util.Map;

public class Environments {

    private static final DynamicVariable<Environment> _current = DynamicVariable.newInstance(null);

    public static <T> T my(Class<T> service) {
        final Environment environment = current();
        if (null == environment) {
            throw new IllegalStateException();
        }
        return environment.provide(service);
    }

    private static Environment current() {
        return _current.value();
    }

    public static void runWith(Environment environment, Runnable runnable) {
        _current.using(environment, runnable);
    }

    public static Environment newClosedEnvironment(final Object... bindings) {
        return new Environment() {

            public <T> T provide(Class<T> service) {
                for (Object binding : bindings) {
                    if (service.isInstance(binding)) {
                        return service.cast(binding);
                    }
                }
                return null;
            }

        };
    }

    public static Environment newCachingEnvironmentFor(final Environment environment) {
        return new Environment() {
            private final Map<Class<?>, Object> _bindings = new HashMap<Class<?>, Object>();

            public <T> T provide(Class<T> service) {
                final Object existing = _bindings.get(service);
                if (null != existing) {
                    return service.cast(existing);
                }
                final T binding = environment.provide(service);
                if (null == binding) {
                    return null;
                }
                _bindings.put(service, binding);
                return binding;
            }
        };
    }

    public static Environment newConventionBasedEnvironment(Object... bindings) {
        return newCachingEnvironmentFor(compose(newClosedEnvironment(bindings), new ConventionBasedEnvironment()));
    }

    public static Environment newConventionBasedEnvironment() {
        return newCachingEnvironmentFor(new ConventionBasedEnvironment());
    }

    public static Environment compose(final Environment... environments) {
        return new Environment() {
            public <T> T provide(Class<T> service) {
                for (Environment e : environments) {
                    final T binding = e.provide(service);
                    if (null != binding) {
                        return binding;
                    }
                }
                return null;
            }
        };
    }

    private static final class ConventionBasedEnvironment implements Environment {
        public <T> T provide(Class<T> service) {
            return resolve(service);
        }

        /**
         * Resolves a service interface to its default implementation using the
         * db4o namespace convention:
         * <p>
         * interface foo.bar.Baz
         * default implementation foo.internal.bar.BazImpl
         *
         * @return the convention based type name for the requested service
         */
        private <T> T resolve(Class<T> service) {
            final String className = defaultImplementationFor(service);
            final Object binding = createInstance(className);
            if (null == binding) {
                throw new IllegalArgumentException("Cant find default implementation for " + service.toString() + ": " + className);
            }
            return service.cast(binding);
        }

        private Object createInstance(String className) {
            try {
                return Class.forName(className).newInstance();
            } catch (InstantiationException e) {
                throw new IllegalStateException(e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(e);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    static String defaultImplementationFor(Class service) {
        if (!service.isInterface()) {
            throw new IllegalArgumentException(service + " is not an interface.");
        }

        final String packageName = service.getPackage().getName();
        return packageName + ".internal." + (service.getSimpleName() + "Impl");
    }

}
